package com.zzyl.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.zzyl.constant.RedisConstant;
import com.zzyl.dto.ResourceDto;
import com.zzyl.entity.Resource;
import com.zzyl.enums.BasicEnum;
import com.zzyl.exception.BaseException;
import com.zzyl.mapper.ResourceMapper;
import com.zzyl.service.ResourceService;
import com.zzyl.utils.EmptyUtil;
import com.zzyl.utils.NoProcessing;
import com.zzyl.utils.ObjectUtil;
import com.zzyl.vo.ResourceVo;
import com.zzyl.vo.TreeItemVo;
import com.zzyl.vo.TreeVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.OptionalInt;
import java.util.stream.Collectors;

/**
 * @author Jun
 * 菜单管理
 */
@Service
public class ResourceServiceImpl implements ResourceService {

    @Autowired
    private ResourceMapper resourceMapper;

    @Autowired
    RedisTemplate<String, String> redisTemplate;

    //资源列表
    @Override
    @Cacheable(cacheNames = RedisConstant.RESOURCE_LIST,key = "#resourceDto.hashCode()")
    public List<ResourceVo> list(ResourceDto resourceDto) {
        Resource resource = BeanUtil.toBean(resourceDto, Resource.class);
        List<Resource> resourceList = resourceMapper.list(resource);
        List<ResourceVo> resourceVoList = BeanUtil.copyToList(resourceList, ResourceVo.class);
        return resourceVoList;
    }

/*    //资源列表
    @Override
    public List<ResourceVo> list(ResourceDto resourceDto) {
        Resource resource = BeanUtil.toBean(resourceDto, Resource.class);
        String s = redisTemplate.opsForValue().get(RedisConstant.RESOURCE_LIST + resourceDto.hashCode());
        if (cn.hutool.core.util.ObjectUtil.isNotEmpty(s)) {
            return JSONUtil.toList(s, ResourceVo.class);
        }
        List<Resource> resourceList = resourceMapper.list(resource);
        List<ResourceVo> resourceVoList = BeanUtil.copyToList(resourceList, ResourceVo.class);
        redisTemplate.opsForValue().set(RedisConstant.RESOURCE_LIST + resourceDto.hashCode(), JSONUtil.toJsonStr(resourceVoList));

        return resourceVoList;
    }*/

    //资源树形
    @Override
    @Cacheable(cacheNames = RedisConstant.RESOURCE_TREE, key = "#resourceDto.hashCode()")
    public TreeVo tree(ResourceDto resourceDto) {
        Resource resource = BeanUtil.toBean(resourceDto, Resource.class);
        resource.setResourceType("m");
        resource.setDataState("0");
        List<Resource> list = resourceMapper.list(resource);
        if (EmptyUtil.isNullOrEmpty(list)) {
            throw new RuntimeException("资源未定义");
        }
        List<ResourceVo> resourceVos = BeanUtil.copyToList(list, ResourceVo.class);

        TreeItemVo treeItemVo = new TreeItemVo();
        treeItemVo.setId("100001000000000");
        treeItemVo.setLabel("智慧养老院");

        children(treeItemVo, resourceVos);

        TreeVo treeVo = new TreeVo(Arrays.asList(treeItemVo));
        return treeVo;
    }

    /**
     * 递归方法
     *
     * @param parent      父级菜单 (id:节点ID-资源编号  label:显示内容  children:显示内容-子节点)
     * @param resourceVos 全部菜单列表
     */
    private void children(TreeItemVo parent, List<ResourceVo> resourceVos) {
        parent.setChildren(
                resourceVos.stream()
                        //过滤菜单的父资源编号和父级菜单的资源编号相等的资源列表（查找父资源菜单的下一级菜单）
                        .filter(resource -> parent.getId().equals(resource.getParentResourceNo()))
                        .map(resource -> {
                            TreeItemVo vo = new TreeItemVo();
                            vo.setId(resource.getResourceNo());
                            vo.setLabel(resource.getResourceName());
                            return vo;
                        }).collect(Collectors.toList())
        );
        //判断上面过滤是否有值，有值则遍历子节点，继续递归
        if (ObjectUtil.isNotEmpty(parent.getChildren())) {
            parent.getChildren().forEach(vo -> {
                children(vo, resourceVos);
            });
        }
    }

    //新增资源
    @Override
    @Caching(evict = {
            @CacheEvict(value = RedisConstant.RESOURCE_LIST, allEntries = true),
            @CacheEvict(value = RedisConstant.RESOURCE_TREE, allEntries = true)
    })
    public void add(ResourceDto resourceDto) {
        Resource resource = BeanUtil.copyProperties(resourceDto, Resource.class);
        //获取父级菜单的状态，新增的子级菜单状态要与父级菜单状态一致
        resource.setDataState(resourceMapper.selectByResourceNo(resource.getParentResourceNo()).getDataState());
        String parentNo = resource.getParentResourceNo();
        if (ObjectUtil.isNotEmpty(parentNo)) {
            String childrenNo = createRespurceNo(parentNo);
            resource.setResourceNo(childrenNo);
            resourceMapper.add(resource);
        }
    }

    /**
     * 启用禁用
     * 要启用父资源，子级菜单一键启用
     * 父资源现在是禁用状态，子菜单不可启用
     * 存在子菜单启用，父级菜单不可禁用
     * 按钮禁用启用除外
     *
     * @param resourceDto
     */
    @Override
    @Caching(evict = {
            @CacheEvict(value = RedisConstant.RESOURCE_LIST, allEntries = true),
            @CacheEvict(value = RedisConstant.RESOURCE_TREE, allEntries = true)
    })
    public void updateState(ResourceDto resourceDto) {
        //前端传入更新参数
        Resource resource = BeanUtil.copyProperties(resourceDto, Resource.class);
        //数据库中该菜单的参数
        Resource resourceSql = resourceMapper.selectByResourceNo(resource.getResourceNo());
        //数据库中该菜单的父级菜单的参数
        Resource parent = resourceMapper.selectByResourceNo(resource.getParentResourceNo());
        if ("m".equals(resourceSql.getResourceType()) && "0".equals(resource.getDataState()) && "1".equals(parent.getDataState())) {
            //父资源菜单禁用状态，不允许启用
            throw new BaseException(BasicEnum.PARENT_MENU_DISABLE);
        }
        List<Resource> list = new ArrayList<>();
        // 获取该菜单下的所有子菜单
        getAllChildren(list, resource);
        // 如果传入的菜单要进行禁用操作，则判断子级菜单是否有启用状态
        if (ObjectUtil.isNotEmpty(list) && "1".equals(resource.getDataState())) {
            //判断子级菜单是否有启用状态，如果有，则抛出异常
            list.forEach(r -> {
                if ("0".equals(r.getDataState())) {
                    throw new BaseException(BasicEnum.SUB_RESOURCE_IS_ACTIVE);
                }
            });
        }
        // 如果传入的菜单要进行启用操作，则启用所有子菜单
        if (ObjectUtil.isNotEmpty(list) && "0".equals(resource.getDataState())) {
            list.forEach(r -> {
                r.setDataState("0");
                resourceMapper.updateState(r);
            });
        }
        // 禁用或启用该菜单
        resourceMapper.updateState(resource);
    }

    /**
     * 递归，获取父级菜单下的所有子菜单
     *
     * @param resource
     */
    private void getAllChildren(List<Resource> list, Resource resource) {
        List<Resource> allChildren = new ArrayList<>();
        allChildren = resourceMapper.getAllChildren(resource.getResourceNo());
        if (ObjectUtil.isNotEmpty(allChildren)) {
            allChildren.forEach(r -> {
                list.add(r);
                getAllChildren(list, r);
            });
        }
    }

    /**
     * 更新菜单
     * 更新数据的父级菜单深度不可>=3
     * 更新数据的父级菜单资源深度不可以超过或等于子级菜单资源
     *
     * @param resourceDto
     */
    @Override
    @Caching(evict = {
            @CacheEvict(value = RedisConstant.RESOURCE_LIST, allEntries = true),
            @CacheEvict(value = RedisConstant.RESOURCE_TREE, allEntries = true)
    })
    public void update(ResourceDto resourceDto) {
        Resource resource = BeanUtil.copyProperties(resourceDto, Resource.class);
        Resource resourceSql = resourceMapper.selectByResourceNo(resource.getResourceNo());
        // 父菜单路径变化
        if (!resourceSql.getParentResourceNo().equals(resource.getParentResourceNo())) {
            String newParentNo = NoProcessing.processString(resource.getParentResourceNo());
            // 判断新的父菜单深度是否大于等于三级
            if (newParentNo.length() / 3 >= 5) {
                // 菜单最多3级
                //    100  001  001  001  001
                // 固定格式  根  一级  二级  三级
                throw new BaseException(BasicEnum.RESOURCE_DEPTH_UPPER_LIMIT);
            }
            //资源路径变化，判断新的父资源路径深度是否大于子资源路径深度
            if (newParentNo.length() / 3 >= NoProcessing.processString(resource.getResourceNo()).length() / 3) {
                throw new BaseException(BasicEnum.MENU_PATH_MISMATCH);
            }
        }
        resourceMapper.update(resource);
    }

    @Override
    @Caching(evict = {
            @CacheEvict(value = RedisConstant.RESOURCE_LIST, allEntries = true),
            @CacheEvict(value = RedisConstant.RESOURCE_TREE, allEntries = true)
    })
    public void delete(String resourceNo) {
        Resource resource = new Resource();
        resource.setParentResourceNo(resourceNo);
        if (ObjectUtil.isNotEmpty(resourceMapper.list(resource))) {
            throw new BaseException(BasicEnum.RESOURCE_HAS_CHILDREN);
        }
        if ("0".equals(resourceMapper.selectByResourceNo(resourceNo).getDataState())) {
            throw new BaseException(BasicEnum.ENABLED_CANNOT_DELETED);
        }
        resourceMapper.delete(resourceNo);
    }

    /**
     * 根据父级菜单创建子级菜单
     *
     * @param parentNo 父资源编号
     * @return 子资源编号
     */
    private String createRespurceNo(String parentNo) {
        //每三位为一组，去掉 '000'的组
        String s = NoProcessing.processString(parentNo);
        if (s.length() / 3 >= 5) {
            //菜单最多3级
            //    100  001  001  001  001
            // 固定格式  根  一级  二级  三级
            throw new BaseException(BasicEnum.RESOURCE_DEPTH_UPPER_LIMIT);
        }
        //查询该节点下是否有子级菜单
        Resource resource = new Resource();
        resource.setParentResourceNo(parentNo);
        List<Resource> list = resourceMapper.list(resource);
        //没有子级菜单
        if (ObjectUtil.isEmpty(list)) {
            return NoProcessing.createNo(parentNo, false);
        }
        //有子级菜单
        Long max = list.stream().mapToLong(r -> {
            return Long.parseLong(r.getResourceNo());
        }).max().orElse(0);
        return NoProcessing.createNo(String.valueOf(max), true);
    }

}
